import logging
import os
from logging.handlers import RotatingFileHandler
from importlib import import_module
import traceback
from ast import literal_eval

import toml
from flask import Flask, jsonify
from flask_cors import CORS
from flask_restx import Api
from flask_migrate import Migrate
from werkzeug.middleware.proxy_fix import ProxyFix
from flask_jwt_extended import JWTManager
from app.api import api

from app.utils.db import DataBaseHelper
from app.models import TokenBlockList, db
from app.utils.exception import BaseError
from settings import BaseConfig

ROOT_PATH = os.path.dirname(os.path.dirname(__file__))

# Add logging config
logging.basicConfig(level=logging.DEBUG)
file_log_handler = RotatingFileHandler(
    os.path.join(ROOT_PATH, 'app', 'log', 'app.log'),
    maxBytes=1024 * 1024 * 100,
    backupCount=2
)
formatter = logging.Formatter('%(levelname)s %(asctime)s %(filename)s:%(lineno)d %(message)s')
file_log_handler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(file_log_handler)


def register_ns_dynamically(api):
    """
    Dynamically register the namespace from all modules from api
    """
    api_path = os.path.join(ROOT_PATH, 'app', 'api')
    for root, _, filenames in os.walk(api_path):
        for filename in filenames:
            if filename == "__init__.py" or not filename.endswith(".py"):
                continue

            module_path = os.path.join(root, filename)
    
            relative_part = os.path.relpath(module_path, api_path).replace(os.sep, '.')
            api_name = '.'.join(os.path.basename(relative_part).split('.')[:-1])
    
            api_module = import_module('app.api.{}'.format(api_name))
            
            try:
                api.add_namespace(api_module.ns, path='/api/v1')
            except:
                traceback.print_exc()


def create_app(config_name='development'):
    """
    Create an application instance
    :param config_name: define current env, defaults to None
    """
    # Create instance
    app = Flask(__name__, static_folder='static')

    # Modify proxy for real address
    app.wsgi_app = ProxyFix(app.wsgi_app)

    # Register api
    # api = Api(app, version="1.0", title="Flask API", description="A simple Flask API", doc="/docs")
    api.init_app(app)
    
    # Dynamically register the namespace
    register_ns_dynamically(api)

    # Register jwt plugin
    jwt = JWTManager(app)

    @jwt.expired_token_loader
    def my_expired_token_callback(header, expired_token):
        """
        自定义过期token的返回结构
        :param header:
        :param expired_token:
        :return:
        """
        return BaseError(msg="用户未登录").jsonify()

    @jwt.token_in_blocklist_loader
    def check_if_token_revoked(jwt_header, jwt_payload: dict) -> bool:
        """
        Callback function to check if a JWT exists in the database blocklist
        """
        jti = jwt_payload["jti"]
        token = db.session.query(TokenBlockList.id).filter_by(jti=jti).scalar()

        return token is not None

    # enable CORS
    CORS(app)
    # read config from toml file
    config_path = os.path.join(ROOT_PATH, 'properties', '{}.toml'.format(config_name))
    app.config.from_file(config_path, load=toml.load)  # 环境配置
    app.config.from_object(BaseConfig())  # 统一配置

    db.init_app(app)
    DataBaseHelper.db = db
    
    Migrate(app, db)
    
    @app.after_request
    def patch_exception(response):
        """
        Flask-restx has overridden error_handler and hasn't found a good way to customize 
        the default validation output, so it makes a processing patch in response
        """
        
        if response.json:
            errors = response.json.get('errors', [])
            
            if response.status_code != 200:
                response.status_code = 200
                
                code = response.json.get('code', 200)
                msg = response.json.get('message', '')

                if errors:
                    code = 422
                    msg = '参数校验失败'
                
                data = {
                    'code': code,
                    'data': response.json.get('data', {}),
                    'msg': msg,
                    'errors': errors
                }
                response.set_data(jsonify(data).get_data())
            
            if errors and isinstance(errors, str):
                errors = literal_eval(errors)
                data = response.json
                data['errors'] = errors
                response.set_data(jsonify(data).get_data())
        return response
    
    return app
